<?php

/**
 * This file is part of the Codizer package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright   Copyright (c) 2010 Advisa (http://www.advisa.fr)
 * @author      Codizer Core Team <team@codizer.org>
 * @package     Codizer
 */

class f_package_Downloader
{
    private static $instance;

    private $url;
    private $packages = array();

    static $INSTALLATION_COMPLETE = 1;
    static $ALREADY_INSTALLED = -50;
    static $INVALID_CHECKSUM = -100;
    static $INVALID_VERSION = -150;
    static $DOWNLOAD_INCOMPLETE = -200;

    static $PACKAGE_PATH;

    private function  __construct()
    {
        self::$PACKAGE_PATH = WEBAPP_DIR.DS.'cache'.DS.'package'.DS;
    }

    /**
     * @return f_package_Downloader
     */
    public static function getInstance()
    {
        if(is_null(self::$instance))
        {
            self::$instance = new self();
        }

        return self::$instance;
    }

    public function upgradePackages()
    {
        // is there updates ?
        if($this->checkForUpdates())
        {
            foreach($this->getUpgradablePackagesList() as $package)
            {
                // first retrieve package
                if($this->retrievePackage($package))
                {
                    // if ok, unzip packages files
                    $install = $this->unzipPackage($package['name'], $package['version']);

                    // if ok, install and upgrade packages
                    if($install === true)
                    {
                        if(f_package_Manager::getInstance()->initOrUpgradePackageByLabel($package['name']))
                        {
                            unlink(self::$PACKAGE_PATH.$package['name'].'-'.$package['version'].'.zip');
                        }
                    }
                }
            }
        }
    }

    public function installPackageFiles($packageName, $version)
    {
        $desc = self::$PACKAGE_PATH.$packageName.'-'.$version.'.xml';
        $zipFileName = $packageName.'-'.$version.'.zip';
        $zip = self::$PACKAGE_PATH.$zipFileName;

        if(is_file($desc) && is_file($zip))
        {
            $xmlDesc = f_xml_Xpath::getInstance($desc);
            $md5 = $xmlDesc->getElementValueFromXpath('md5checksum');
            $version = $xmlDesc->getElementValueFromXpath('version');
            $zipName = $xmlDesc->getElementValueFromXpath('zipName');

            if($zipFileName == $zipName)
            {
                if(md5_file($zip) == $md5)
                {
                    if(version_compare($this->getInstalledPackageVersion($packageName), $version, '<'))
                    {
                        $this->unzipPackage($packageName, $version);
                        return self::$INSTALLATION_COMPLETE;
                    }
                    else
                    {
                        return self::$ALREADY_INSTALLED;
                    }
                }
                else
                {
                    return self::$INVALID_CHECKSUM;
                }
            }
            else
            {
                return self::$INVALID_VERSION;
            }
        }
        else
        {
            return self::$DOWNLOAD_INCOMPLETE;
        }
    }

    private function unzipPackage($packageName, $version, $unlink = false)
    {
        $fPath = self::$PACKAGE_PATH.$packageName.'-'.$version.'.zip';

        if(is_file($fPath))
        {
            $zip = new ZipArchive();

            if($zip->open($fPath) === true)
            {
                $zip->extractTo(BASE_DIR);
                if($zip->close())
                {
                    if($unlink === true)
                    {
                        unlink($fPath);
                    }

                    return true;
                }
            }
        }

        return false;
    }


    /**
     * @param string $url
     * @return f_package_Downloader
     */
    public function fromRepository($url)
    {
        $this->url = $url;
        $this->retrievePackagesList();
        return $this;
    }

    public function getPackagesList()
    {
        return $this->packages;
    }

    public function checkForUpdates()
    {
        if(isset($this->packages['upgradable']))
        {
            return count($this->packages['upgradable']) > 0;
        }
        return false;
    }

    public function getUpgradablePackagesList()
    {
        return $this->packages['upgradable'];
    }

    private function retrievePackagesList()
    {
        if(!is_null($this->url))
        {
            $fh = fopen($this->url, 'r');

            while(is_resource($fh) && !feof($fh))
            {
                $resp .= fread($fh, 1024);
            }

            $xml = f_xml_Xpath::getInstance()->fromString($resp);

            $packages = $xml->fromXpath('package')->getElements();

            $packManager = f_package_Manager::getInstance();

            foreach($packages as $package)
            {
                /* @var $package f_xml_XmlElement */
                $res = $package->getAttributes();
                $res['md5'] = $package->getSubElement('md5')->getContent();
                $res['url'] = $package->getSubElement('url')->getContent();
                $res['date'] = $package->getSubElement('date')->getContent();

                $installedVersion = $packManager->getInstalledPackageVersion($res['name']);

                if(!is_null($installedVersion))
                {
                    if(version_compare($installedVersion, $res['version'], '<'))
                    {
                        $this->packages['upgradable'][] = $res;
                    }
                    else
                    {
                        $this->packages['upToDate'][] = $res;
                    }
                }
                else
                {
                    $this->packages['installable'][] = $res;
                }                
            }
        }
        else
        {
            throw new Exception('No repository url setted');
        }
    }

    private function retrievePackage($packageDescription)
    {
        $filePath = self::$PACKAGE_PATH.$packageDescription['name'].'-'.$packageDescription['version'].'.zip';
        $fp = fopen($filePath, 'w+');
        $content = $this->curl_get_file_contents($packageDescription['url']);
        $this->fwrite_stream($fp, $content);
        fclose($fp);

        if(md5_file($filePath) == $packageDescription['md5'])
        {
            return true;
        }

        return false;
    }

    private function curl_get_file_contents($url)
    {
        $c = curl_init();
        curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($c, CURLOPT_URL, $url);
        $contents = curl_exec($c);
        curl_close($c);

        if($contents)
        {
            return $contents;
        }
        else
        {
            return false;
        }
    }

    private function fwrite_stream($fp, $string)
    {
        for ($written = 0; $written < strlen($string); $written += $fwrite)
        {
            $fwrite = fwrite($fp, substr($string, $written));
            if ($fwrite === false)
            {
                return $fwrite;
            }
        }
        return $written;
    }
}